/*
*	Copyright (C) 2014  Julio Montes, Mario Carrillo
*	
*	Julio Montes e-mail: imc.coder@gmail.com 
*
*	Mario Carrillo e-mail: mario_90ago@live.com.mx
*
*	BSD 3-Clause License
*/

#include "alliance/include/projectstree.h"

#include "alliance/include/path.h"
#include "alliance/include/projectmgr.h"

#include "include/iaEditorManager.h"
#include "include/iaManager.h"

#include <wx/imaglist.h>
#include <wx/tokenzr.h>

#define FILE_SWITCH_TO_FILE 1001
#define FILE_SAVE_FILE 1002
#define FILE_CLOSE_FILE 1003
#define FILE_REMOVE_FILE_FROM_PROJECT 1004
#define FILE_OPEN_FILE 1005
#define FILE_RENAME_FILE 1006

#define PROJECT_ACTIVE_PROJECT 2001
#define PROJECT_SAVE_PROJECT 2002
#define PROJECT_CLOSE_PROJECT 2003
#define PROJECT_ADD_FILES 2004
#define PROJECT_BUILD 2005
#define PROJECT_BUILD_OPTIONS 2006


namespace alliance
{
    IMPLEMENT_DYNAMIC_CLASS( ProjectsTree, wxTreeCtrl );

    ProjectsTree::ProjectsTree()
        : m_itemData( NULL )
    {

    }

    ProjectsTree::ProjectsTree( wxWindow *parent, const wxString &workspaceName )
        : wxTreeCtrl( parent, wxID_ANY )
        , m_itemData( NULL )
    {
        wxImageList *imageList = new wxImageList;
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "workspace.png" ), wxBITMAP_TYPE_PNG ) );
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "project.png" ), wxBITMAP_TYPE_PNG ) );
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "file.png" ), wxBITMAP_TYPE_PNG ) );
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "file_deleted.png" ), wxBITMAP_TYPE_PNG ) );
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "file_modified.png" ), wxBITMAP_TYPE_PNG ) );
        imageList->Add( wxBitmap( Path::s_dirImages + _T( "virtual_dir.png" ), wxBITMAP_TYPE_PNG ) );

        AssignImageList( imageList );

        AddRoot( workspaceName, WORKSPACE_IMG );

        Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_ACTIVATED, wxTreeEventHandler( ProjectsTree::OnWorkspaceTreeActivated ) );
        Connect( wxID_ANY, wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK, wxTreeEventHandler( ProjectsTree::OnWorkspaceTreeRightClick ) );
        Connect( FILE_SWITCH_TO_FILE, FILE_RENAME_FILE, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( ProjectsTree::OnPopupMenuFile ), NULL, this );
        Connect( PROJECT_ACTIVE_PROJECT, PROJECT_BUILD_OPTIONS, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( ProjectsTree::OnPopupMenuProject ), NULL, this );
        //iaManager::Get()->GetMainFrame()->
    }

    ProjectsTree::~ProjectsTree()
    {
        m_mapElements.clear();
    }

    int ProjectsTree::OnCompareItems( const wxTreeItemId &item1, const wxTreeItemId &item2 )
    {
        ItemData *data1 = static_cast<ItemData *>( GetItemData( item1 ) );
        ItemData *data2 = static_cast<ItemData *>( GetItemData( item2 ) );

        if( data1->type == VIRTUAL_DIR )
        {
            if( data2->type != VIRTUAL_DIR )
            {
                return -1;
            }

            return ( data1->path < data2->path ? -1 : 1 );
        }

        if( data2->type == VIRTUAL_DIR )
        {
            return 1;
        }

        return ( data1->path.AfterLast( _T( '/' ) ) < data2->path.AfterLast( _T( '/' ) ) ? -1 : 1 );
    }

    void ProjectsTree::DeactivateAllProjects()
    {
        wxTreeItemIdValue cookie;
        wxTreeItemId current = GetFirstChild( GetRootItem(), cookie );
        if( current.IsOk() )
        {
            SetItemBold( current, false );
            current = GetNextChild( GetRootItem(), cookie );
            while( current.IsOk() )
            {
                SetItemBold( current, false );
                current = GetNextChild( GetRootItem(), cookie );
            }
        }
    }

    void ProjectsTree::ActivateProject( const Project *project )
    {
        Freeze(); //for update UI

        const std::map<wxString, wxTreeItemId>::const_iterator &it = m_mapElements.find( project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            DeactivateAllProjects();
            SetItemBold( it->second, true );
            EnsureVisible( it->second );
        }

        Thaw(); //for update UI
    }

    bool ProjectsTree::IsActiveProject( const Project *project ) const
    {
        const std::map<wxString, wxTreeItemId>::const_iterator &it = m_mapElements.find( project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            return IsBold( it->second );
        }

        return false;
    }

    void ProjectsTree::AddProject( Project *project )
    {
        const wxString &projectFilename = project->GetFilename();
        ItemData *itemData = new ItemData;
        itemData->path = projectFilename;
        itemData->type = PROJECT;
        itemData->project = project;

        const wxTreeItemId &projectId = AppendItem( GetRootItem(), project->GetName(), PROJECT_IMG, PROJECT_IMG, itemData );
        m_mapElements[projectFilename] = projectId;

        //add files
        const std::vector<projectFile> &vectorProjectFiles = project->GetFiles();
        for( size_t i=0; i<vectorProjectFiles.size(); ++i )
        {
            AddFile( vectorProjectFiles[i].filename, vectorProjectFiles[i].virtualDirectory, project );
        }
    }

    void ProjectsTree::ReloadProject( Project *project )
    {
        const std::map<wxString, wxTreeItemId>::const_iterator &it = m_mapElements.find( project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            DeleteChildren( it->second );
            const std::vector<projectFile> &vectorProjectFiles = project->GetFiles();
            for( size_t i=0; i<vectorProjectFiles.size(); ++i )
            {
                AddFile( vectorProjectFiles[i].filename, vectorProjectFiles[i].virtualDirectory, project );
            }
        }
    }

    void ProjectsTree::AddFile( const wxString &filePath, const wxString &virtualDir, Project *project )
    {
        std::map<wxString, wxTreeItemId>::const_iterator it = m_mapElements.find( filePath + wxT( "-" ) + project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            RemoveFile( filePath, project );
        }

        wxTreeItemId parentId;
        if( !virtualDir.IsEmpty() )
        {
            AddVirtualDir( virtualDir, project );
            parentId = m_mapElements[virtualDir + wxT( "-" ) + project->GetFilename() ];
        }
        else
        {
            //if the virtual Directory is empty, add the file to project node
            parentId = m_mapElements[project->GetFilename()];
        }

        ItemData *itemData = new ItemData;
        itemData->path = filePath;
        itemData->type = FILE;
        itemData->project = project;
        wxTreeItemId fileId;
        if( wxFileExists( filePath ) )
        {
            fileId = AppendItem( parentId, filePath.AfterLast( _T( '/' ) ), FILE_IMG, FILE_IMG, itemData );
        }
        else
        {
            fileId = AppendItem( parentId, filePath.AfterLast( _T( '/' ) ), FILE_DELETED_IMG, FILE_DELETED_IMG, itemData );
        }

        m_mapElements[filePath + wxT( "-" ) + project->GetFilename()] = fileId;
        SortChildren( parentId );
        EnsureVisible( fileId );
    }

    void ProjectsTree::AddVirtualDir( const wxString &virtualDir, Project *project )
    {
        const wxString &projectFilename = project->GetFilename();
        std::map<wxString, wxTreeItemId>::const_iterator it = m_mapElements.find( virtualDir + _T( "-" ) + projectFilename );
        if( it != m_mapElements.end() )
        {
            return;
        }

        wxStringTokenizer st( virtualDir, _T( "/\\" ), wxTOKEN_STRTOK );
        wxString currentVirtualDir;
        wxTreeItemId currectId = m_mapElements[projectFilename];
        while( st.HasMoreTokens() )
        {
            const wxString &dirName = st.GetNextToken();
            currentVirtualDir += dirName + wxT( "/" );
            std::map<wxString, wxTreeItemId>::const_iterator it = m_mapElements.find( currentVirtualDir + _T( "-" ) + projectFilename );

            if( it != m_mapElements.end() )
            {
                currectId = it->second;
            }
            else
            {
                ItemData *itemData = new ItemData;
                itemData->path = currentVirtualDir;
                itemData->type = VIRTUAL_DIR;
                itemData->project = project;
                currectId = AppendItem( currectId, dirName, VIRTUAL_DIR_IMG, VIRTUAL_DIR_IMG, itemData );
                m_mapElements[currentVirtualDir + wxT( "-" ) + projectFilename] = currectId;
            }
        }
    }

    bool ProjectsTree::RemoveFile( const wxString &filePath, const Project *project )
    {
        std::map<wxString, wxTreeItemId>::iterator it = m_mapElements.find( filePath + _T( "-" ) + project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            Delete( it->second );
            //m_mapElements.erase( it ); //not is necessary erase in ItemData destructor
            return true;
        }

        return false;
    }

    bool ProjectsTree::RemoveProject( const Project *project )
    {
        std::map<wxString, wxTreeItemId>::iterator it = m_mapElements.find( project->GetFilename() );
        if( it != m_mapElements.end() )
        {
            Delete( it->second );
            //m_mapElements.erase( it ); //not is necessary erase in ItemData destructor
            return true;
        }

        return false;
    }

    void ProjectsTree::OnWorkspaceTreeRightClick( wxTreeEvent &evt )
    {
        ItemData *itemData = dynamic_cast<ItemData *>( GetItemData( evt.GetItem() ) );
        if( itemData != NULL )
        {
            switch( itemData->type )
            {
                case PROJECT:
                    ShowPopupMenuProject( itemData );
                break;

                case FILE:
                    ShowPopupMenuFile( itemData );
                break;

                case VIRTUAL_DIR:
                break;

                default:
                break;
            }
        }
    }

    void ProjectsTree::OnWorkspaceTreeActivated( wxTreeEvent &evt )
    {
        ItemData *itemData = dynamic_cast<ItemData *>( GetItemData( evt.GetItem() ) );
        if( itemData != NULL )
        {
            switch( itemData->type )
            {
                case PROJECT:
                    ProjectManager::Get()->ActiveProject( itemData->project );
                break;

                case FILE:
                    if( !wxFileExists( itemData->path ) )
                    {
                        if( GetItemImage( evt.GetItem() ) != FILE_DELETED_IMG )
                        {
                            SetItemImage( evt.GetItem(), FILE_DELETED_IMG );
                            SetItemImage( evt.GetItem(), FILE_DELETED_IMG, wxTreeItemIcon_Selected );
                        }
                        wxMessageBox( wxT( "The file " ) + itemData->path + wxT( " not exists" ), wxT( "Open file" ), wxICON_ERROR );
                    }
                    else
                    {
                        iaManager::Get()->GetEditorManager()->OpenFile( itemData->path, itemData->path.AfterLast( _T( '/' ) ), ProjectManager::s_imageFile );
                        if( GetItemImage( evt.GetItem() ) == FILE_DELETED_IMG )
                        {
                            SetItemImage( evt.GetItem(), FILE_IMG );
                            SetItemImage( evt.GetItem(), FILE_IMG, wxTreeItemIcon_Selected );
                        }
                    }
                break;

                default:
                break;
            }

            if( itemData->type == VIRTUAL_DIR || itemData->type == PROJECT )
            {
                if( IsExpanded( evt.GetItem() ) )
                {
                    Collapse( evt.GetItem() );
                }
                else
                {
                    Expand( evt.GetItem() );
                }
            }
        }
    }

    void ProjectsTree::OnPopupMenuFile( wxCommandEvent &evt )
    {
        iaEditorManager *editorMgr = iaManager::Get()->GetEditorManager();
        switch( evt.GetId() )
        {
            case FILE_SWITCH_TO_FILE:
                editorMgr->SetSelectionEditor( m_itemData->path );
            break;

            case FILE_SAVE_FILE:
                editorMgr->SaveEditor( m_itemData->path );
            break;

            case FILE_CLOSE_FILE:
                editorMgr->CloseEditor( m_itemData->path );
            break;

            case FILE_REMOVE_FILE_FROM_PROJECT:
                if( editorMgr->CloseEditor( m_itemData->path ) )
                {
                    m_itemData->project->RemoveFile( m_itemData->path );
                    RemoveFile( m_itemData->path, m_itemData->project );
                }
            break;

            case FILE_OPEN_FILE:
                editorMgr->OpenFile( m_itemData->path, m_itemData->path.AfterLast( _T( '/' ) ), ProjectManager::s_imageFile );
            break;

            case FILE_RENAME_FILE:
            {
                wxString oldName = GetItemText( m_itemData->GetId() );
                wxString newName = wxGetTextFromUser( wxT( "New name:" ), wxT( "Rename file" ), oldName, NULL ).Trim().Trim( false );
                if( !newName.IsEmpty() && newName != oldName && newName.Find( _T( '/' ) ) == wxNOT_FOUND && newName.Find( _T( '\\' ) ) == wxNOT_FOUND )
                {
                    const wxString newFilePath = m_itemData->path.BeforeLast( _T( '/' ) ) + wxT( "/" ) + newName;
                    if( m_itemData->project->RenameFile( m_itemData->path, newFilePath ) )
                    {
                        RemoveFile( newFilePath, m_itemData->project );
                        SetItemText( m_itemData->GetId(), newName );
                        SortChildren( GetItemParent( m_itemData->GetId() ) );
                    }
                }
            }
            break;
        }
    }

    void ProjectsTree::ShowPopupMenuFile( ItemData *itemData )
    {
        wxMenu menu;
        const wxString filePath = itemData->path;
        const wxString filename = GetItemText( itemData->GetId() );
        const bool isOpened = ( iaManager::Get()->GetEditorManager()->GetEditor( filePath ) != NULL );

        if( isOpened )
        {
            menu.Append( FILE_SWITCH_TO_FILE, wxT( "switch to " ) + filename );
            menu.AppendSeparator();
            menu.Append( FILE_SAVE_FILE, wxT( "save " ) + filename );
            menu.Append( FILE_CLOSE_FILE, wxT( "close " ) + filename );
        }
        else
        {
            menu.Append( FILE_OPEN_FILE, wxT( "open " ) + filename );
            menu.AppendSeparator();
            menu.Append( FILE_RENAME_FILE, wxT( "rename file" ) );
        }

        menu.AppendSeparator();
        menu.Append( FILE_REMOVE_FILE_FROM_PROJECT, wxT( "remove file from project" ) );

        m_itemData = itemData;

        PopupMenu( &menu );
    }

    void ProjectsTree::ShowPopupMenuProject( ItemData *itemData )
    {
        wxMenu menu;
        const wxString filePath = itemData->path;
        const wxString filename = GetItemText( itemData->GetId() );
        const Project *project = itemData->project;
        const Project *activeProject = ProjectManager::Get()->GetActiveProject();
        const bool isActive = activeProject->GetFilename() == project->GetFilename();
        const bool isModified = project->IsModified();

        if( !isActive )
        {
            menu.Append( PROJECT_ACTIVE_PROJECT, wxT( "active " ) + filename );
            menu.AppendSeparator();
        }
        else
        {
            menu.Append( PROJECT_BUILD, wxT( "build " ) + filename );
            menu.AppendSeparator();
        }
        if( isModified )
        {
            menu.Append( PROJECT_SAVE_PROJECT, wxT( "save project" ) );
            menu.AppendSeparator();
        }

        menu.Append( PROJECT_CLOSE_PROJECT, wxT( "close " ) + filename );
        menu.AppendSeparator();
        menu.Append( PROJECT_ADD_FILES, wxT( "add files" ) );
        menu.AppendSeparator();
        menu.Append( PROJECT_BUILD_OPTIONS, wxT( "build option" ) );

        m_itemData = itemData;

        PopupMenu( &menu );
    }

    void ProjectsTree::OnPopupMenuProject( wxCommandEvent &evt )
    {
        switch( evt.GetId() )
        {
            case PROJECT_ACTIVE_PROJECT:
                ProjectManager::Get()->ActiveProject( m_itemData->project );
            break;

            case PROJECT_SAVE_PROJECT:
                ProjectManager::Get()->SaveProject( m_itemData->project );
            break;

            case PROJECT_CLOSE_PROJECT:
                ProjectManager::Get()->CloseProject( m_itemData->project );
            break;

            case PROJECT_ADD_FILES:
                ProjectManager::Get()->AddFiles( m_itemData->project );
            break;

            case PROJECT_BUILD:
                ProjectManager::Get()->Build(); //always builds the active project
            break;

            case PROJECT_BUILD_OPTIONS:
                ProjectManager::Get()->BuildOptions( m_itemData->project );
            break;
        }
    }

    ItemData::~ItemData()
    {
        ProjectsTree *tree = ProjectManager::Get()->GetProjectsTree();
        if( tree != NULL )
        {
            if( type != PROJECT )
            {
                tree->m_mapElements.erase( path + _T( "-" ) + project->GetFilename() );
            }
            else
            {
                tree->m_mapElements.erase( path );
            }
        }
    }
}
